package org.yeasy.report.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import org.yeasy.common.util.CommonBeanUtil;
import org.yeasy.common.util.CustomDateUtil;
import org.yeasy.common.util.CustomDecimalUtil;
import org.yeasy.report.bean.ColumnSetting;
import org.yeasy.report.bean.DynamicColumn;
import org.yeasy.report.bean.DynamicResult;
import org.yeasy.report.constant.ColumnType;
import org.yeasy.report.constant.DateConstant;
import org.yeasy.report.constant.MarkConstant;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

/**
 * The type My dynamic report util.涉及到动态列的表格
 *
 * @author yangyishe
 * @date 2022年08月31日 15:17
 */
public class DynamicReportUtil {
    /**
     * Calc accumulation group only month list.仅按照月份进行累加
     * 如果要连续使用汇总和累加,则最好在汇总的基础上直接引用代码,否则效率会比较低
     *
     * @param <T>                  the type parameter
     * @param dataList             the data list
     * @param beforeData           the before data
     * @param columnSettingList    the report column setting list
     * @param includeDynamicColumn the include dynamic column
     * @return the list
     * @author yangyishe
     * @date 2022年08月31日 16:29
     */
    public static <T extends Serializable> List<T> calc2AccumulationGroupMonth(List<T> dataList,
                                                                               T beforeData,
                                                                               List<ColumnSetting> columnSettingList,
                                                                               boolean includeDynamicColumn) {
        List<String> lstDateField = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.DATE);
        String strDateField = lstDateField.get(0);
        Function<T, Date> calcDate = o -> CustomDateUtil.convert2Date(CommonBeanUtil.invokeGetter(o, strDateField));

        List<T> lstSumGroup = calc2SumGroupMonth(dataList, columnSettingList, includeDynamicColumn);
        lstSumGroup = lstSumGroup.stream().sorted(Comparator.comparing(calcDate)).collect(Collectors.toList());

        Set<String> setCalcField = ColumnUtil.calc2FieldSet(columnSettingList, ColumnType.CALC);
        BinaryOperator<T> calcMerge = (t, t2) -> {
            T mMerge = ObjectUtil.clone(t);
            if (includeDynamicColumn) {
                Set<String> setDynamicField1 = DynamicColumnUtil.calcJson2DynamicFields((JSONObject) t);
                setCalcField.addAll(setDynamicField1);
                Set<String> setDynamicField2 = DynamicColumnUtil.calcJson2DynamicFields((JSONObject) t2);
                setCalcField.addAll(setDynamicField2);
            }
            for (String calcField : setCalcField) {
                BigDecimal dec1 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t, calcField));
                BigDecimal dec2 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t2, calcField));
                CommonBeanUtil.invokeSetter(mMerge, calcField, dec1.add(dec2));
            }
            ColumnUtil.mergeObj(mMerge, t, t2, columnSettingList);
            Date mAnyDate = CustomDateUtil.convert2Date(CommonBeanUtil.invokeGetter(t2, strDateField));
            Date mMonthEnd = DateUtil.beginOfDay(DateUtil.endOfMonth(mAnyDate));
            CommonBeanUtil.invokeSetter(mMerge, strDateField, mMonthEnd);

            return mMerge;
        };

        List<T> lstAccumulation = new ArrayList<>();
        T mAccumulation = ObjectUtil.clone(beforeData);
        for (T mSum : lstSumGroup) {
            mAccumulation = calcMerge.apply(mAccumulation, mSum);
            lstAccumulation.add(ObjectUtil.clone(mAccumulation));
        }

        return lstAccumulation;
    }

    /**
     * Calc 2 dynamic column list.计算动态列
     *
     * @param <T>           the type parameter
     * @param dataList      the data list
     * @param key2LabelFunc the key 2 label func
     * @return the list
     * @author yangyishe
     * @date 2022年09月07日 15:24
     */
    public static <T extends Serializable> List<DynamicColumn> calc2DynamicColumn(List<T> dataList, UnaryOperator<String> key2LabelFunc) {
        if (dataList.isEmpty()) {
            return new ArrayList<>();
        }
        T obj = dataList.get(0);
        Class<? extends Serializable> clazz = obj.getClass();
        if (clazz == JSONObject.class) {
            return dataList.stream()
                    .map(JSONObject.class::cast)
                    .map(DynamicColumnUtil::calcJson2DynamicFields)
                    .flatMap(Collection::stream)
                    .distinct()
                    .sorted(String::compareTo)
                    .map(s -> {
                        DynamicColumn mColumn = new DynamicColumn();
                        mColumn.setField(s);
                        String strKey = DynamicColumnUtil.calcDynamicField2Key(s);
                        mColumn.setKey(strKey);
                        mColumn.setLabel(key2LabelFunc.apply(strKey));
                        return mColumn;
                    })
                    .collect(Collectors.toList());
        } else {
            Set<String> setField = CommonBeanUtil.calc2AllFieldName(obj);
            Set<String> setActiveField = new HashSet<>();
            for (String field : setField) {
                for (T t : dataList) {
                    if (CommonBeanUtil.invokeGetter(t, field) != null) {
                        setActiveField.add(field);
                        break;
                    }
                }
            }
            List<DynamicColumn> lstDynamicColumn = new ArrayList<>();
            for (String field : setActiveField) {
                String label = key2LabelFunc.apply(field);
                if (CharSequenceUtil.isNotEmpty(label)) {
                    DynamicColumn mColumn = new DynamicColumn();
                    mColumn.setField(field);
                    mColumn.setKey(field);
                    mColumn.setLabel(label);
                    lstDynamicColumn.add(mColumn);
                }
            }

            return lstDynamicColumn.stream().sorted(Comparator.comparing(DynamicColumn::getField)).collect(Collectors.toList());
        }


    }

    /**
     * Calc 2 dynamic list list.
     * 示例:
     * `{part:1,code:'1001',name:'现金'}`=>
     * `{'_dyn_code_1':'1001','_dyn_name_1':'现金'}`
     *
     * @param <T>            the type parameter
     * @param dataList       the data list
     * @param keyField       the key field
     * @param valueFieldList the value field list
     * @return the list
     * @author yangyishe
     * @date 2022年10月17日 10:41
     */
    public static <T extends Serializable> List<JSONObject> calc2DynamicList(List<T> dataList, String keyField, List<String> valueFieldList) {
        List<JSONObject> lstJson = CommonBeanUtil.bean2JsonBatch(dataList);
        Map<String, Function<JSONObject, String>> mapValueField2CalcDynamicField = valueFieldList
                .stream()
                .collect(Collectors.toMap(s -> s,
                        s -> (t -> DynamicColumnUtil.calcKey2DynamicField(CommonBeanUtil.invokeGetter(t, keyField), s))));
        for (JSONObject mJson : lstJson) {
            for (Map.Entry<String, Function<JSONObject, String>> valueField2CalcDynamicField : mapValueField2CalcDynamicField.entrySet()) {
                String valueField = valueField2CalcDynamicField.getKey();
                Function<JSONObject, String> calcJson2DynamicField = valueField2CalcDynamicField.getValue();
                CommonBeanUtil.invokeSetter(mJson, calcJson2DynamicField.apply(mJson), CommonBeanUtil.invokeGetter(mJson, valueField));
            }
        }

        return lstJson;
    }

    /**
     * Calc dynamic column json list list.计算动态列的json数据集合
     * 此方法仅能处理一种动态列(keyField->valueField)
     * 如商品信息为`{code:'SP001',name:'商品一号'}`,动态列处理后增加属性`{'_dyn_SP001':'商品一号'}`
     * 如果需要多组动态列,可在此基础上扩展
     *
     * @param <T>        the type parameter
     * @param dataList   the data list
     * @param keyField   the key field
     * @param valueField the value field
     * @return the list
     * @author yangyishe
     * @date 2022年08月31日 15:01
     */
    public static <T extends Serializable> List<JSONObject> calc2DynamicList(List<T> dataList, String keyField, String valueField) {
        List<JSONObject> lstJson = CommonBeanUtil.bean2JsonBatch(dataList);
        Function<JSONObject, String> calcJson2DynamicField = t -> DynamicColumnUtil.calcKey2DynamicField(CommonBeanUtil.invokeGetter(t, keyField));
        for (JSONObject mJson : lstJson) {
            CommonBeanUtil.invokeSetter(mJson, calcJson2DynamicField.apply(mJson), CommonBeanUtil.invokeGetter(mJson, valueField));
        }
        return lstJson;
    }

    /**
     * Calc 2 dynamic result dynamic result.
     *
     * @param <T>           the type parameter
     * @param dataList      the data list
     * @param key2LabelFunc the key 2 label func
     * @return the dynamic result
     * @author yangyishe
     * @date 2022年09月07日 15:30
     */
    public static <T extends Serializable> DynamicResult<T> calc2DynamicResult(List<T> dataList, UnaryOperator<String> key2LabelFunc) {
        DynamicResult<T> mResult = new DynamicResult<>();
        mResult.setList(dataList);
        List<DynamicColumn> lstDynamicColumn = calc2DynamicColumn(dataList, key2LabelFunc);
        mResult.setColumns(lstDynamicColumn);
        return mResult;
    }

    /**
     * Calc 2 merge list list.
     *
     * @param jsonList        the json list
     * @param splitColumnList the split column list
     * @return the list
     * @author yangyishe
     * @date 2022年10月17日 11:27
     */
    public static List<JSONObject> calc2MergeList(List<JSONObject> jsonList, List<String> splitColumnList) {
        List<String> lstSplitColumnSort = splitColumnList.stream().sorted().collect(Collectors.toList());
        Function<JSONObject, String> calc2UniqueKey = jsonObject -> lstSplitColumnSort.stream()
                .map(s -> jsonObject.get(s).toString())
                .reduce("", (a, b) -> (a + " " + b))
                .trim();
        Map<String, JSONObject> mapKey2Json = new HashMap<>();
        for (JSONObject mJson : jsonList) {
            String strUniqueKey = calc2UniqueKey.apply(mJson);
            JSONObject mJsonOld = mapKey2Json.get(strUniqueKey);
            if (mJsonOld == null) {
                mapKey2Json.put(strUniqueKey, mJson);
            } else {
                for (Map.Entry<String, Object> key2Val : mJson.entrySet()) {
                    mJsonOld.putIfAbsent(key2Val.getKey(), key2Val.getValue());
                }

            }
        }

        return new ArrayList<>(mapKey2Json.values());
    }

    /**
     * Calc accumulation month list.仅按照月份进行汇总
     *
     * @param <T>                  the type parameter
     * @param dataList             the data list
     * @param columnSettingList    the report column setting list
     * @param includeDynamicColumn the include dynamic column
     * @return the list
     * @author yangyishe
     * @date 2022年08月31日 15:17
     */
    public static <T extends Serializable> List<T> calc2SumGroupMonth(List<T> dataList,
                                                                      List<ColumnSetting> columnSettingList,
                                                                      boolean includeDynamicColumn) {

        List<String> lstDateField = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.DATE);
        String strDateField = lstDateField.get(0);

        Map<String, List<T>> mapMonth2DataList = new HashMap<>();
        for (T mData : dataList) {
            Date mOpeningDate = CustomDateUtil.convert2Date(CommonBeanUtil.invokeGetter(mData, strDateField));
            String strMonth = DateUtil.format(mOpeningDate, DateConstant.DEFAULT_MONTH_PATTERN);
            mapMonth2DataList.putIfAbsent(strMonth, new ArrayList<>());
            mapMonth2DataList.get(strMonth).add(mData);
        }

        Set<String> setCalcField = ColumnUtil.calc2FieldSet(columnSettingList, ColumnType.CALC);
        BinaryOperator<T> calcMerge = (t, t2) -> {
            T mMerge = ObjectUtil.clone(t);
            if (includeDynamicColumn) {
                Set<String> setDynamicField1 = DynamicColumnUtil.calcJson2DynamicFields((JSONObject) t);
                setCalcField.addAll(setDynamicField1);
                Set<String> setDynamicField2 = DynamicColumnUtil.calcJson2DynamicFields((JSONObject) t2);
                setCalcField.addAll(setDynamicField2);
            }
            for (String calcField : setCalcField) {
                BigDecimal dec1 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t, calcField));
                BigDecimal dec2 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t2, calcField));
                CommonBeanUtil.invokeSetter(mMerge, calcField, dec1.add(dec2));
            }
            ColumnUtil.mergeObj(mMerge, t, t2, columnSettingList);
            Object mAnyDateFirst = CommonBeanUtil.invokeGetter(t2, strDateField);
            Date mAnyDate = CustomDateUtil.convert2Date(mAnyDateFirst);
            Date mMonthEnd = DateUtil.beginOfDay(DateUtil.endOfMonth(mAnyDate));
            CommonBeanUtil.invokeSetter(mMerge, strDateField, mMonthEnd);

            return mMerge;
        };

        List<T> lstResult = new ArrayList<>();
        for (List<T> objListEach : mapMonth2DataList.values()) {
            Optional<T> optSum = objListEach.stream().reduce(calcMerge);
            optSum.ifPresent(t -> lstResult.add(ObjectUtil.clone(t)));
        }

        return lstResult;
    }

    /**
     * Calc 2 sum group setting list.根据setting进行汇总
     *
     * @param <T>                  the type parameter
     * @param dataList             the data list
     * @param columnSettingList    the report column setting list
     * @param includeDynamicColumn the include dynamic column
     * @return the list
     * @author yangyishe
     * @date 2022年09月01日 10:54
     */
    public static <T extends Serializable> List<T> calc2SumGroupSetting(List<T> dataList,
                                                                        List<ColumnSetting> columnSettingList,
                                                                        boolean includeDynamicColumn) {
        Set<String> setGroupField = ColumnUtil.calc2FieldSet(columnSettingList, ColumnType.GROUP);
        Function<T, String> calcGroupKey = t -> setGroupField.stream()
                .map(s -> CommonBeanUtil.invokeGetter(t, s).toString())
                .reduce("", (a, b) -> (a + MarkConstant.DEFAULT_UNIQUE_CHAIN_SEPARATOR + b).trim());
        Map<String, List<T>> mapKey2List = new HashMap<>();
        for (T t : dataList) {
            String strKey = calcGroupKey.apply(t);
            mapKey2List.putIfAbsent(strKey, new ArrayList<>());
            mapKey2List.get(strKey).add(t);
        }

        Set<String> setCalcField = ColumnUtil.calc2FieldSet(columnSettingList, ColumnType.CALC);
        BinaryOperator<T> calcMerge = (t, t2) -> {
            T mMerge = ObjectUtil.clone(t);
            if (includeDynamicColumn) {
                Set<String> setDynamicField1 = DynamicColumnUtil.calcJson2DynamicFields((JSONObject) t);
                setCalcField.addAll(setDynamicField1);
                Set<String> setDynamicField2 = DynamicColumnUtil.calcJson2DynamicFields((JSONObject) t2);
                setCalcField.addAll(setDynamicField2);
            }
            for (String calcField : setCalcField) {
                BigDecimal dec1 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t, calcField));
                BigDecimal dec2 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t2, calcField));
                CommonBeanUtil.invokeSetter(mMerge, calcField, dec1.add(dec2));
            }
            ColumnUtil.mergeObj(mMerge, t, t2, columnSettingList);

            return mMerge;
        };

        List<T> lstResult = new ArrayList<>();
        for (List<T> objListEach : mapKey2List.values()) {
            Optional<T> optSum = objListEach.stream().reduce(calcMerge);
            optSum.ifPresent(t -> lstResult.add(ObjectUtil.clone(t)));
        }

        return lstResult;
    }

}
